﻿/* This package contains a powerful, declarative command-line parsing system in a single .cs file. 
 * You can include this in any project with almost zero footprint and very easy/readable usage, 
 * as shown below. More switch types, including generic ones are coming soon. Visit the project 
 * page for a sample of how to use this handy package. Part of Code Blocks (http://codeblocks.codeplex.com)  
 */

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.IO;
using System.Reflection;

static class CommandLine
{
    private interface IValType
    {
        Type GetValType { get; }
    }
    public abstract class SwitchBase
    {
        protected SwitchBase()
        {
        }

        public string Name
        {
            get;
            protected set;
        }
        public string ShortForm
        {
            get;
            protected set;
        }

        public string Description { get; set; }

        public object DefaultValue { get; set; }

        public bool Optional { get; protected set; }

        public abstract int InvokeHandler(IEnumerable<object> values);
    }

    public abstract class SwitchBase<T> : SwitchBase, IValType // Class that encapsulates switch data.
    {
        protected SwitchBase(string name, Action<IEnumerable<T>> handler, string shortForm)
        {
            Name = name;
            Handler = handler;
            ShortForm = shortForm;
        }

        protected SwitchBase(string name, Action<IEnumerable<T>> handler)
        {
            Name = name;
            Handler = handler;
            ShortForm = null;
        }

        public Action<IEnumerable<T>> Handler
        {
            get;
            private set;
        }

        public override int InvokeHandler(IEnumerable<object> values)
        {
            Handler(values.Cast<T>());
            return Optional ? 0 : 1;
        }

        new public T DefaultValue
        {
            get { return (T)base.DefaultValue; }
            set { base.DefaultValue = value; }
        }

        Type IValType.GetValType
        {
            get { return typeof(T); }
        }
    }

    public sealed class Switch : SwitchBase<string>
    {
        public Switch(string name, Action<IEnumerable<string>> handler, string shortForm)
            : base(name, handler, shortForm)
        {
        }

        public Switch(string name, Action<IEnumerable<string>> handler)
            : base(name, handler)
        {
        }

        new public bool Optional
        {
            get { return base.Optional; }
            set { base.Optional = value; }
        }
    }

    public sealed class OptionalSwitch : SwitchBase<string>
    {
        public OptionalSwitch(string name, Action<IEnumerable<string>> handler, string shortForm)
            : base(name, handler, shortForm)
        {
            base.Optional = true;
        }

        public OptionalSwitch(string name, Action<IEnumerable<string>> handler)
            : base(name, handler)
        {
            base.Optional = true;
        }

        new bool Optional { get { return true; } }
    }

    public sealed class MandatorySwitch : SwitchBase<string>
    {
        public MandatorySwitch(string name, Action<IEnumerable<string>> handler, string shortForm)
            : base(name, handler, shortForm)
        {
            base.Optional = false;
        }

        public MandatorySwitch(string name, Action<IEnumerable<string>> handler)
            : base(name, handler)
        {
            base.Optional = false;
        }

        new bool Optional { get { return false; } }
    }

    public sealed class Switch<T> : SwitchBase<T>
    {
        public Switch(string name, Action<IEnumerable<T>> handler, string shortForm)
            : base(name, handler, shortForm)
        {
        }

        public Switch(string name, Action<IEnumerable<T>> handler)
            : base(name, handler)
        {
        }
    }

    public sealed class OptionalSwitch<T> : SwitchBase<T>
    {
        public OptionalSwitch(string name, Action<IEnumerable<T>> handler, string shortForm)
            : base(name, handler, shortForm)
        {
            base.Optional = true;
        }

        public OptionalSwitch(string name, Action<IEnumerable<T>> handler)
            : base(name, handler)
        {
            base.Optional = true;
        }

        new bool Optional { get { return true; } }
    }

    public sealed class MandatorySwitch<T> : SwitchBase<T>
    {
        public MandatorySwitch(string name, Action<IEnumerable<T>> handler, string shortForm)
            : base(name, handler, shortForm)
        {
            base.Optional = false;
        }

        public MandatorySwitch(string name, Action<IEnumerable<T>> handler)
            : base(name, handler)
        {
            base.Optional = false;
        }

        new bool Optional { get { return false; } }
    }

    /* The regex that extracts names and comma-separated values for switches 
    in the form (<switch>[="value 1",value2,...])+ */
    private static readonly Regex ArgRegex =
        new Regex(@"(?<name>[^=]+)=?((?<quoted>\""?)(?<value>(?(quoted)[^\""]+|[^,]+))\""?,?)",
            RegexOptions.Compiled | RegexOptions.CultureInvariant |
            RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);

    private const string NameGroup = "name"; // Names of capture groups
    private const string ValueGroup = "value";

    private static string BuildUsageString(params SwitchBase[] switches)
    {
        var builder = new StringBuilder();
        builder.AppendFormat("Usage is {0} ", Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().CodeBase));
        var argUsages = from s in switches
                        select string.Format("{0}=<{1} {2}>", s.ShortForm == null ? s.Name : s.Name + "|" + s.ShortForm,
                                                                s.Description ?? string.Empty,
                                                                s.DefaultValue == null ? string.Empty : "(" + s.DefaultValue + ")");
        builder.Append(string.Join(" ", argUsages));

        return builder.ToString();
    }

    private static int Process(IEnumerable<KeyValuePair<string, string>> args, params SwitchBase[] switches)
    {
        /* Run through all matches in the argument list and if any of the switches 
        match, get the values and invoke the handler we were given. We do a Sum() 
        here for 2 reasons:
         * a) To actually run the handlers
         * b) return the count of invoked handlers (each returns 1 if invoked). */
        return (from arg in args
                from s in switches
                let destType = (s as IValType).GetValType
                where
                   ((string.Compare(arg.Key, s.Name, true) == 0) ||
                    (string.Compare(arg.Key, s.ShortForm, true) == 0))
                select s.InvokeHandler(
                   from val in arg.Value.Split(',')
                   let typeConverter = TypeDescriptor.GetConverter(destType)
                   where ((typeConverter != null) && (typeConverter.CanConvertFrom(val.GetType())))
                   select typeConverter.ConvertFrom(val)
                )).Sum();
    }

    private sealed class ArgComparer : IEqualityComparer<KeyValuePair<string, string>>
    {
        public bool Equals(KeyValuePair<string, string> x, KeyValuePair<string, string> y)
        {
            return x.Key == y.Key;
        }

        public int GetHashCode(KeyValuePair<string, string> obj)
        {
            return obj.GetHashCode();
        }
    }

    private static readonly ArgComparer _argComparer = new ArgComparer();

    public static bool Process(Action printUsage, params SwitchBase[] switches)
    {
        try
        {
            var appSettings = ConfigurationManager.AppSettings;
            var appSettingsArgs = from i in Enumerable.Range(0, appSettings.Count)
                                  let arg = string.Format("{0}={1}", appSettings.GetKey(i), appSettings[i])
                                  let match = ArgRegex.Match(arg)
                                  where match.Success
                                  select new KeyValuePair<string, string>(match.Groups[NameGroup].Value, match.Groups[ValueGroup].Value);
            var commandlineArgs = from arg in Environment.GetCommandLineArgs().Skip(1)
                                  let match = ArgRegex.Match(arg)
                                  where match.Success
                                  select new KeyValuePair<string, string>(match.Groups[NameGroup].Value, match.Groups[ValueGroup].Value);
            var combined = commandlineArgs.Union(appSettingsArgs, _argComparer);
            if (Process(combined, switches) < (from s in switches select s.Optional ? 0 : 1).Sum())
            {
                printUsage(); // We didn't find some mandatory switches
                return false;
            }
            else
                return true;
        }
        catch
        {
            printUsage();
            return false;
        }
    }

    public static bool Process(this string[] args, Action<string> printUsage, params SwitchBase[] switches)
    {
        return Process(args, () => printUsage(BuildUsageString(switches)), switches);
    }

    public static bool Process(this IEnumerable<string> args, Action printUsage, params SwitchBase[] switches)
    {
        var parsedArgs = from arg in Environment.GetCommandLineArgs().Skip(1)
                         let match = ArgRegex.Match(arg)
                         where match.Success
                         select new KeyValuePair<string, string>(match.Groups[NameGroup].Value, match.Groups[ValueGroup].Value);
        if (Process(parsedArgs, switches) < (from s in switches select s.Optional ? 0 : 1).Sum())
        {
            printUsage(); // We didn't find some mandatory switches
            return false;
        }
        else
            return true;
    }
}